/*
 *@Type NormalStore.java
 * @Desc
 * @Author urmsone urmsone@163.com
 * @date 2024/6/13 02:07
 * @version
 */
package service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import model.command.Command;
import model.command.CommandPos;
import model.command.RmCommand;
import model.command.SetCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.CommandUtil;
import utils.LoggerUtil;
import utils.RandomAccessFileUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class NormalStore implements Store {

    public static final String TABLE = ".table";
    public static final String RW_MODE = "rw";
    public static final String NAME = "data";
    public static final String BACK_UP = "backup";
    public static final String WAL_TMP = "WAL_TMP";
    public static int tableFileCount;
    private final Logger LOGGER = LoggerFactory.getLogger(NormalStore.class);
    private final String logFormat = "[NormalStore][{}]: {}";


    /**
     * 内存表，类似缓存
     */
    private TreeMap<String, Command> memTable;

    /**
     * hash索引，存的是数据长度和偏移量
     * */
    private HashMap<String, CommandPos> index;

    /**
     * 数据目录
     */
    private final String dataDir;

    /**
     * 读写锁，支持多线程，并发安全写入
     */
    private final ReadWriteLock indexLock;

    /**
     * 暂存数据的日志句柄
     */
    private RandomAccessFile writerReader;

    /**
     * 持久化阈值 1MB
     */
    private final int storeThreshold = 128;
    /*
    *内存表最大条数
    */
    private final int memTableThreshold;


    private final String LogFileName;
    public NormalStore(String dataDir) {
        this.dataDir = dataDir;
        this.LogFileName=this.dataDir + File.separator+"logfile.txt";
        this.indexLock = new ReentrantReadWriteLock();
        this.memTable = new TreeMap<String, Command>();
        this.index = new HashMap<>();
        this.memTableThreshold =3;

        // 创建目录
        File file = new File(dataDir);
        if (!file.exists()) {
            LoggerUtil.info(LOGGER,logFormat, "NormalStore","dataDir isn't exist,creating...");
            file.mkdirs();
        }

        //计算该目录下有多少个.table结尾的文件
        getFile();

        this.redoLog();
        System.out.println(this.memTable);
        for (int i = 1; i <= tableFileCount; i++) {
            this.reloadIndex(this.dataDir + File.separator + NAME + i + TABLE,i);
        }
    }
    public void redoLog() {
        File file = new File(LogFileName);
        if (file.exists() && file.length() > 0) {
            System.out.println("文件存在数据");
            try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    line = line.replaceAll("[\"{}]", "");
                    String[] parts = line.split(": ", 2);
                    if (parts.length == 2) {
                        String key = parts[0].trim();
                        String values = parts[1].trim();
                        String[] splitValue = values.split(",");
                        String[] key2 = splitValue[0].split(":");
                        String[] type = splitValue[1].split(":");
                        if(type[1].equals("SET")){
                            String[] value = splitValue[2].split(":");
                            this.memTable.put(key, new SetCommand(key2[1],value[1]));
                        } else if(type[1].equals("RM") ){
                            this.memTable.put(key, new RmCommand(key2[1]));
                        }
                    } else {
                        System.out.println("Invalid data format in line(无效数据格式): " + line);
                    }
                }
            } catch (IOException e) {
                System.out.println("Error reading log file(读取数据出错): " + e.getMessage());
            }

        } else {
            System.out.println("logfile文件不存在数据.");
        }
    }

    public void getFile(){
        File file = new File(this.dataDir);

        String extension = ".table";

        File[] tableFiles = file.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                //返回文件名以.table结尾
                return name.toLowerCase().endsWith(extension);
            }
        });

        // 计算.table文件的数量
        tableFileCount = (tableFiles != null && tableFiles.length != 0) ? tableFiles.length : 1;
    }

    public String genFilePath() {
        return this.dataDir + File.separator + NAME + tableFileCount + TABLE;
    }


    //磁盘文件达到最大值之后，新开文件
    public void rotate(){
        File file = new File(this.genFilePath());
        if(file.length() > storeThreshold) {
            //压缩文件
            compression();
            tableFileCount++;
            //创建新文件
            Path path = Paths.get(this.genFilePath());
            try {
                Files.createFile(path);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //压缩文件
    public void compression(){
        try {
            //切换只读文件，以供其他线程读
                //将数据文件的内容复制到只读文件中
                RandomAccessFileUtil.copyFileUsingRandomAccessFile(this.genFilePath(),this.dataDir + File.separator + NAME +
                        tableFileCount+ WAL_TMP + TABLE);
                writerReader = new RandomAccessFile(this.dataDir + File.separator + NAME +
                        tableFileCount+ WAL_TMP + TABLE,"r");
            //建立一个备份文件，用来存储压缩之后的数据
            String backupFilePath = this.dataDir + File.separator + BACK_UP + TABLE;
            for(Map.Entry<String,CommandPos> element: index.entrySet()){
                CommandPos value = element.getValue();
                if(value.getNo() == tableFileCount){
                    byte[] command = RandomAccessFileUtil.readByIndex(this.genFilePath(), value.getPos(), value.getLen());
                    if(command != null) {
                        //写入备份文件,先写数据长度
                        RandomAccessFileUtil.writeInt(backupFilePath, value.getLen());
                        RandomAccessFileUtil.write(backupFilePath, command);
                    }
                }
            }

            Path sourcePath = Paths.get(backupFilePath);
            Path targetPath = Paths.get(this.genFilePath());
            //将备份数据文件重命名为旧的数据文件并覆盖,备份完成

            Files.move(sourcePath,targetPath, StandardCopyOption.REPLACE_EXISTING);

            //更新索引
            reloadIndex(this.genFilePath(),tableFileCount);

            //文件压缩完成,删除只读文件
                writerReader.close();
                Files.deleteIfExists(Paths.get(this.dataDir + File.separator + NAME +
                        tableFileCount+ WAL_TMP + TABLE));
                writerReader = null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //加载索引
        public void reloadIndex(String path,int no) {
        RandomAccessFile file = null;
        try {
            file = new RandomAccessFile(path, RW_MODE);
            long len = file.length();
            long start = 0;
            //偏移量
            file.seek(start);
            while (start < len) {
                int cmdLen = file.readInt();
                byte[] bytes = new byte[cmdLen];
                file.read(bytes);
                JSONObject value = JSON.parseObject(new String(bytes, StandardCharsets.UTF_8));
                Command command = CommandUtil.jsonToCommand(value);
                start += 4;
                if (command != null) {
                    CommandPos cmdPos = new CommandPos((int) start, cmdLen,no);
                    //添加数据长度和偏移量
                    index.put(command.getKey(), cmdPos);
                }
                start += cmdLen;
            }
            file.seek(file.length());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        LoggerUtil.debug(LOGGER, logFormat, "reload index: "+index.toString());
    }

    //添加和更新操作
    @Override
    public void set(String key, String value) {
        try {
            SetCommand command = new SetCommand(key, value);
            byte[] commandBytes = JSONObject.toJSONBytes(command);
            // 加锁
            indexLock.writeLock().lock();
            String n = String.valueOf(memTable.size());
            System.out.println("memTable已经有的数据有"+n+"条");
            memTable.put(n, command);
            // 写table（wal）文件
            try (FileWriter writer = new FileWriter(LogFileName, true)) {
                String line = n + ": " + command + "\n";
                writer.write(line);
                System.out.println("Data written to logfile.txt");
            }
            catch (IOException e) {
            System.out.println("An error occurred while writing to the log file.");
            e.printStackTrace();
        }
            List<Command> commandList = new ArrayList<>(memTable.values());
           // Collections.reverse(commandList);
            if (memTable.size()>memTableThreshold){
                for (Command command2 : commandList) {
                    byte[] commandBytes2 = JSONObject.toJSONBytes(command2);
                    RandomAccessFileUtil.writeInt(this.genFilePath(), commandBytes2.length);
                    int pos =RandomAccessFileUtil.write(this.genFilePath(), commandBytes2);
                    CommandPos cmdPos = new CommandPos(pos, commandBytes2.length,tableFileCount);
                    index.put(command2.getKey(), cmdPos);
                    rotate();
                    TreeMap<String,Command> oldTable=this.memTable;
                    this.memTable=new TreeMap<String,Command>();
                    FileWriter writer = new FileWriter(LogFileName);
                }
            }
        } catch (Throwable t){
            throw new RuntimeException(t);
        } finally {
            indexLock.writeLock().unlock();
        }
    }
    //查询
    @Override
    public String get(String key) {
        try {
            //上锁
            indexLock.readLock().lock();
            List<Command> commandList = new ArrayList<>(memTable.values());
            Collections.reverse(commandList);
            for (Command command2 :commandList) {
                if (command2 instanceof SetCommand) {
                    SetCommand setCommand = (SetCommand) command2;
                    if (setCommand.getKey().equals(key)) {
                        return setCommand.getValue();
                    }
                } else if (command2 instanceof RmCommand) {
                    RmCommand rmCommand = (RmCommand) command2;
                    if(rmCommand.getKey().equals(key)) {
                        System.out.println("rmCommand.getKey()"+rmCommand.getKey());
                        return null;
                    }
                }
            }
            // 从索引中获取信息
            CommandPos cmdPos = index.get(key);
            if (cmdPos == null) {
                return null;
            }
            byte[] commandBytes;

            if(writerReader != null && cmdPos.getNo() == tableFileCount){
                //另一个线程正在执行压缩操作
                commandBytes = RandomAccessFileUtil.readRandomAccessFileByIndex(writerReader, cmdPos.getPos(), cmdPos.getLen());

            }else {
                 commandBytes = RandomAccessFileUtil.readByIndex(this.dataDir + File.separator + NAME + cmdPos.getNo()+ TABLE, cmdPos.getPos(), cmdPos.getLen());
            }
            JSONObject value = JSONObject.parseObject(new String(commandBytes));
            Command cmd = CommandUtil.jsonToCommand(value);
            if (cmd instanceof SetCommand) {
                return ((SetCommand) cmd).getValue();
            }
            if (cmd instanceof RmCommand) {
                return null;
            }
        } catch (Throwable t) {
            throw new RuntimeException(t);
        } finally {
            indexLock.readLock().unlock();
        }
        return null;
    }


    @Override
    public boolean rm(String key) {
        try {
            RmCommand command = new RmCommand(key);
            byte[] commandBytes = JSONObject.toJSONBytes(command);
            // 加锁
            indexLock.writeLock().lock();
            if(this.get(key)==null){
                System.out.println("rm返回false");
                return false;
            }
            // TODO://先写内存表，内存表达到一定阀值再写进磁盘
            String n = String.valueOf(memTable.size());
            System.out.println("memTable已经有的数据有"+n+"条");
            memTable.put(n, command);
            System.out.println(memTable);
            // 写table（wal）文件
            try (FileWriter writer = new FileWriter(LogFileName, true)) {
                String line = n + ": " + command + "\n";
                writer.write(line);
                System.out.println("Data written to logfile.txt");
            }
            catch (IOException e) {
                System.out.println("An error occurred while writing to the log file.");
                e.printStackTrace();
            }
            List<Command> commandList = new ArrayList<>(memTable.values());
            //Collections.reverse(commandList);
            if (memTable.size()>memTableThreshold){
                for (Command command2 : commandList) {
                    byte[] commandBytes2 = JSONObject.toJSONBytes(command2);
                    RandomAccessFileUtil.writeInt(this.genFilePath(), commandBytes2.length);
                    int pos =RandomAccessFileUtil.write(this.genFilePath(), commandBytes2);
                    CommandPos cmdPos = new CommandPos(pos, commandBytes2.length,tableFileCount);
                    index.put(command2.getKey(), cmdPos);
                    rotate();
                    TreeMap<String,Command> oldTable=this.memTable;
                    this.memTable=new TreeMap<String,Command>();
                    FileWriter writer = new FileWriter(LogFileName);
                }
            }
            return true;
        } catch (Throwable t) {
            throw new RuntimeException(t);
        } finally {
            indexLock.writeLock().unlock();
        }
    }

    @Override
    public void close(){
        for (Command command2 : memTable.values()) {
            byte[] commandBytes2 = JSONObject.toJSONBytes(command2);
            RandomAccessFileUtil.writeInt(this.genFilePath(), commandBytes2.length);
            int pos =RandomAccessFileUtil.write(this.genFilePath(), commandBytes2);
            CommandPos cmdPos = new CommandPos(pos, commandBytes2.length,tableFileCount);
        }
        File file = new File("LogFileName");
        // 使用delete()方法来删除文件
        if (file.delete()) {
            System.out.println("文件删除成功");
        } else {
            System.out.println("文件删除失败");
        }
    }
}
